package com.channel.connector.ctripos.service.impl;

import com.alibaba.fastjson.JSON;
import com.channel.connector.ctripos.common.constant.CtripOSCommonConstants;
import com.channel.connector.ctripos.common.constant.CtripOSCommonUtil;
import com.channel.connector.ctripos.common.constant.InitData;
import com.channel.connector.ctripos.common.util.CtripOSErrorBuilder;
import com.channel.connector.ctripos.common.util.CtripOSPreBookingUtil;
import com.channel.connector.ctripos.common.util.DateUtil;
import com.channel.connector.ctripos.common.util.JAXBUtil;
import com.channel.connector.ctripos.common.util.OrderInterfaceInvoker;
import com.channel.connector.ctripos.common.util.StringUtil;
import com.channel.connector.ctripos.config.OrderCallConfig;
import com.channel.connector.ctripos.domain.CtriposMapRateplanDO;
import com.channel.connector.ctripos.domain.CtriposShopInfoDO;
import com.channel.connector.ctripos.dto.ResponseDTO;
import com.channel.connector.ctripos.dto.bms.OrderRequestDTO;
import com.channel.connector.ctripos.dto.bms.SupplyProductPriceDTO;
import com.channel.connector.ctripos.dto.bms.request.CreateOrderProductRequestDTO;
import com.channel.connector.ctripos.dto.bms.request.CreateOrderRequestDTO;
import com.channel.connector.ctripos.dto.bms.response.SupplyProductPriceResponseDTO;
import com.channel.connector.ctripos.dto.request.IncrementDTO;
import com.channel.connector.ctripos.dto.request.PushConfirmNoRequest;
import com.channel.connector.ctripos.dto.request.QueryRatePlanMappingRequestDTO;
import com.channel.connector.ctripos.dto.response.HotelGuestCountDTO;
import com.channel.connector.ctripos.enums.CtripOSErrorEnum;
import com.channel.connector.ctripos.enums.CtripOSMappingStatusEnum;
import com.channel.connector.ctripos.enums.CtripOSTypeEnum;
import com.channel.connector.ctripos.enums.CurrencyEnum;
import com.channel.connector.ctripos.enums.ErrorCodeEnum;
import com.channel.connector.ctripos.enums.FCerrorEnum;
import com.channel.connector.ctripos.enums.OrderStatusEnum;
import com.channel.connector.ctripos.enums.UniqueIDEnum;
import com.channel.connector.ctripos.mapper.CtriposCommonMapper;
import com.channel.connector.ctripos.service.CtripOSRemoteInvokeService;
import com.channel.connector.ctripos.service.OrderSyncService;
import com.channel.connector.ctripos.webservice.confirmnum.ArrayOfHotelReservationIDType;
import com.channel.connector.ctripos.webservice.confirmnum.CompanyNameType;
import com.channel.connector.ctripos.webservice.confirmnum.HotelReservationIDType;
import com.channel.connector.ctripos.webservice.confirmnum.HotelReservationType;
import com.channel.connector.ctripos.webservice.confirmnum.OTAHotelResNumUpdateRQ;
import com.channel.connector.ctripos.webservice.confirmnum.OTAHotelResNumUpdateRS;
import com.channel.connector.ctripos.webservice.confirmnum.POSType;
import com.channel.connector.ctripos.webservice.confirmnum.ResGlobalInfoType;
import com.channel.connector.ctripos.webservice.confirmnum.SourceType;
import com.channel.connector.ctripos.webservice.order.common.Base;
import com.channel.connector.ctripos.webservice.order.common.BasicPropertyInfo;
import com.channel.connector.ctripos.webservice.order.common.GuestCount;
import com.channel.connector.ctripos.webservice.order.common.HotelReservation;
import com.channel.connector.ctripos.webservice.order.common.HotelReservationID;
import com.channel.connector.ctripos.webservice.order.common.Rate;
import com.channel.connector.ctripos.webservice.order.common.RatePlan;
import com.channel.connector.ctripos.webservice.order.common.RatePlanDescription;
import com.channel.connector.ctripos.webservice.order.common.ResGlobalInfo;
import com.channel.connector.ctripos.webservice.order.common.RoomRate;
import com.channel.connector.ctripos.webservice.order.common.RoomStay;
import com.channel.connector.ctripos.webservice.order.common.RoomType;
import com.channel.connector.ctripos.webservice.order.common.Total;
import com.channel.connector.ctripos.webservice.order.request.OTACancelRQ;
import com.channel.connector.ctripos.webservice.order.request.OTAHotelAvailRQ;
import com.channel.connector.ctripos.webservice.order.request.OTAHotelResRQ;
import com.channel.connector.ctripos.webservice.order.request.OTAReadRQ;
import com.channel.connector.ctripos.webservice.order.request.dto.ContactPerson;
import com.channel.connector.ctripos.webservice.order.request.dto.Criterion;
import com.channel.connector.ctripos.webservice.order.request.dto.Customer;
import com.channel.connector.ctripos.webservice.order.request.dto.HotelRef;
import com.channel.connector.ctripos.webservice.order.request.dto.ListItem;
import com.channel.connector.ctripos.webservice.order.request.dto.PersonName;
import com.channel.connector.ctripos.webservice.order.request.dto.Pos;
import com.channel.connector.ctripos.webservice.order.request.dto.ReadRequest;
import com.channel.connector.ctripos.webservice.order.request.dto.RequestorID;
import com.channel.connector.ctripos.webservice.order.request.dto.ResGuest;
import com.channel.connector.ctripos.webservice.order.request.dto.RoomStayCandidate;
import com.channel.connector.ctripos.webservice.order.request.dto.SpecialRequest;
import com.channel.connector.ctripos.webservice.order.response.OTACancelRS;
import com.channel.connector.ctripos.webservice.order.response.OTAHotelAvailRS;
import com.channel.connector.ctripos.webservice.order.response.OTAHotelResRS;
import com.channel.connector.ctripos.webservice.order.response.OTAReadRS;
import com.channel.connector.ctripos.webservice.order.response.base.RSHeader;
import com.channel.connector.ctripos.webservice.order.response.dto.AmountPercent;
import com.channel.connector.ctripos.webservice.order.response.dto.CancelPenalty;
import com.channel.connector.ctripos.webservice.order.response.dto.Deadline;
import com.channel.connector.ctripos.webservice.order.response.dto.Error;
import com.channel.connector.ctripos.webservice.order.response.dto.HotelStay;
import com.channel.connector.ctripos.webservice.order.response.dto.MealsIncluded;
import com.channel.connector.ctripos.webservice.order.response.dto.PenaltyDescription;
import com.channel.connector.ctripos.webservice.order.response.dto.UniqueID;
import com.channel.connector.ctripos.webservice.order.response.dto.Warning;
import com.channel.connector.order.domain.OrderDO;
import com.channel.connector.order.mapper.OrderMapper;
import com.channel.connector.product.domain.ChannelAgentDO;
import com.channel.connector.product.domain.PriceAndQuotaDO;
import com.channel.connector.product.domain.PricePlanDO;
import com.channel.connector.product.service.PricePlanService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import tk.mybatis.mapper.entity.Example;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @program: hotel-delivery-feizhu-os-server
 * @description: 订单同步服务
 * @author: zhanwang
 * @create: 2018-11-19 11:04
 **/
@Service
@Slf4j
public class OrderSyncServiceImpl implements OrderSyncService {

    @Autowired
    private CtriposCommonMapper ctriposCommonMapper;

    @Autowired
    private PricePlanService pricePlanService;

    @Autowired
    private OrderCallConfig orderCallConfig;

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private CtripOSRemoteInvokeService ctripOSRemoteInvokeService;


    @Override
    public OTAHotelAvailRS checkAvailability(OTAHotelAvailRQ otaHotelAvailRQ) {
        OTAHotelAvailRS otaHotelAvailRS = new OTAHotelAvailRS();
        otaHotelAvailRS.setEchoToken(CtripOSCommonUtil.buildDefaultToken());
        otaHotelAvailRS.setVersion(CtripOSCommonConstants.CTRIP_OS_IF_VERSION.toString());
        Date start = new Date();

        try {
            // 1. 检验参数
            CtriposShopInfoDO shopInfoDO = checkCheckAvailabilityParam(otaHotelAvailRQ, otaHotelAvailRS);
            if (!CollectionUtils.isEmpty(otaHotelAvailRS.getErrors())) {
                return otaHotelAvailRS;
            }

            // 2. 组装试预定请求参数
            Criterion criterion = otaHotelAvailRQ.getAvailRequestSegments().get(0).getHotelSearchCriteria().getCriterion();
            String hotelCode = criterion.getHotelRef().getHotelCode();
            Long roomTypeCode = criterion.getRoomStayCandidates().get(0).getRoomTypeCode() == null ? null : Long.valueOf(criterion.getRoomStayCandidates().get(0).getRoomTypeCode());
            Long commodityId = CollectionUtils.isEmpty(criterion.getRatePlanCandidates()) ? null : Long.valueOf(criterion.getRatePlanCandidates().get(0).getRatePlanCode());
            Integer quantity = criterion.getRoomStayCandidates().get(0).getQauantity();
            Date startDate = DateUtil.stringToDate(criterion.getStayDateRange().getStart());
            Date endDate = DateUtil.stringToDate(criterion.getStayDateRange().getEnd());
            Long nights = DateUtil.getDay(startDate, endDate);
            HotelGuestCountDTO guestCountDTO = getAdultOrChildCount(criterion.getRoomStayCandidates().get(0).getGuestCounts());
            Integer adultCount = guestCountDTO.getAdultCount();
            Integer childCount = guestCountDTO.getChildCount();
            Integer defaultCurrency = CurrencyEnum.CNY.key;

            // 3. 查询已映射、已上架的商品列表
            QueryRatePlanMappingRequestDTO ratePlanMappingRequestDTO = new QueryRatePlanMappingRequestDTO();
            ratePlanMappingRequestDTO.setHotelId(Long.valueOf(hotelCode));
            ratePlanMappingRequestDTO.setRoomTypeId(roomTypeCode);
            ratePlanMappingRequestDTO.setCommodityId(commodityId);
            ratePlanMappingRequestDTO.setMerchantCode(shopInfoDO.getMerchantCode());
            ratePlanMappingRequestDTO.setMapStatus(CtripOSMappingStatusEnum.Active.key);
            ratePlanMappingRequestDTO.setIsOnSale(1);
            List<CtriposMapRateplanDO> rateplanDOS = ctriposCommonMapper.queryRatePlanMapping(ratePlanMappingRequestDTO);
            if (CollectionUtils.isEmpty(rateplanDOS)) {
                otaHotelAvailRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_3, CtripOSErrorEnum.CtripOSError_436, "No mappinged comodities"));
                return otaHotelAvailRS;
            }

            // 4. 试预定
            if (StringUtils.isEmpty(roomTypeCode) || StringUtils.isEmpty(commodityId)) {
                // 4.1 如果只有hotelCode，则查该酒店下所有报价
                // 4.2 如果只有hotelCode、roomTypeCode，则查该酒店房型下所有报价
                Map<Long, CtriposMapRateplanDO> commodityMap = new HashMap<>();
                for (CtriposMapRateplanDO ratePlanMappingPO : rateplanDOS) {
                    commodityMap.put(ratePlanMappingPO.getCommodityId().longValue(), ratePlanMappingPO);
                }
                // 将每个商品不同日期归到同一个map
                Map<Long, List<PriceAndQuotaDO>> priceAndQuotaMap = new HashMap<>();
                for (Map.Entry<Long, CtriposMapRateplanDO> commodityEntry : commodityMap.entrySet()) {
                    // 查询价格、配额
                    IncrementDTO incrementDTO = new IncrementDTO();
                    incrementDTO.setMHotelId(Long.valueOf(hotelCode));
                    incrementDTO.setMRatePlanId(Long.valueOf(commodityEntry.getKey()));
                    incrementDTO.setStartDate(DateUtil.dateToString(startDate));
                    incrementDTO.setEndDate(DateUtil.dateToString(endDate));
                    List<PriceAndQuotaDO> priceAndQuotaDOList = pricePlanService.queryPriceAndQuota(incrementDTO, shopInfoDO.getMerchantCode());

                    if (CollectionUtils.isEmpty(priceAndQuotaDOList)) {
                        continue;
                    }
                    priceAndQuotaMap.put(commodityEntry.getKey(), priceAndQuotaDOList);
                }
                if (MapUtils.isEmpty(priceAndQuotaMap)) {
                    otaHotelAvailRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_3, CtripOSErrorEnum.CtripOSError_436, "Comodity detail no found"));
                    return otaHotelAvailRS;
                }
                assemblyCheckAvailabilityForHotelResponse(otaHotelAvailRS, priceAndQuotaMap, quantity, shopInfoDO.getMerchantCode(),
                        defaultCurrency, commodityMap, nights, endDate, adultCount, childCount);
            } else {
                // 4.3 如果有hotelCode、roomTypeCode、pricePlanCode，则走试预定接口

                // 查询价格、配额
                IncrementDTO incrementDTO = new IncrementDTO();
                incrementDTO.setMHotelId(Long.valueOf(hotelCode));
                incrementDTO.setMRatePlanId(commodityId);
                incrementDTO.setStartDate(DateUtil.dateToString(startDate));
                incrementDTO.setEndDate(DateUtil.dateToString(endDate));
                List<PriceAndQuotaDO> priceAndQuotaDOList = pricePlanService.queryPriceAndQuota(incrementDTO, shopInfoDO.getMerchantCode());

                //组装下单参数
                CreateOrderRequestDTO preBookingRequestDTO = buildPreBookingRequestDTO(otaHotelAvailRQ, shopInfoDO, priceAndQuotaDOList);

                //调用远程接口下单
                ResponseDTO<Collection<SupplyProductPriceResponseDTO>> preBookingResponse = OrderInterfaceInvoker.preBooking(orderCallConfig, JSON.toJSONString(preBookingRequestDTO));
                if (null == preBookingResponse || 1 != preBookingResponse.getResult()) {
                    log.info("携程海外试预定失败, ReturnMsg{}", null == preBookingResponse ? null : preBookingResponse.getFailReason());
                    otaHotelAvailRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_3,
                            CtripOSErrorEnum.CtripOSError_322, preBookingResponse == null ? "PreBooking return null" : preBookingResponse.getFailReason()));
                    return otaHotelAvailRS;
                }

                //结果转换成携程格式
                assemblyCheckAvailabilityForCommodityResponse(otaHotelAvailRS, rateplanDOS.get(0), preBookingResponse,
                        defaultCurrency, nights, quantity, adultCount, childCount);
            }

            // 5. 组装响应结果
            if (!CollectionUtils.isEmpty(otaHotelAvailRS.getRoomStays())) {
                HotelStay hotelStay = new HotelStay();
                BasicPropertyInfo basicPropertyInfo = new BasicPropertyInfo();
                basicPropertyInfo.setHotelCode(hotelCode);
                basicPropertyInfo.setHotelName(rateplanDOS.get(0).getHotelNameEng());
                hotelStay.setBasicPropertyInfo(basicPropertyInfo);
                otaHotelAvailRS.getHotelStays().add(hotelStay);
            }
            if (CollectionUtils.isEmpty(otaHotelAvailRS.getErrors())) {
                otaHotelAvailRS.setSuccess("");
            }

        } catch (Exception e) {
            otaHotelAvailRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_3, CtripOSErrorEnum.CtripOSError_448, FCerrorEnum.FC_ERROR_4.getName()));
            log.error("checkAvailability exception.", e);
        } finally {
            // 6. 记日志
            log.info("checkAvailability request:{},response:{}", JAXBUtil.jaxbBeanToXml(otaHotelAvailRQ), JAXBUtil.jaxbBeanToXml(otaHotelAvailRS));
        }

        return otaHotelAvailRS;
    }

    /**
     * 构建试预定请求参数
     */
    private CreateOrderRequestDTO buildPreBookingRequestDTO(OTAHotelAvailRQ otaHotelAvailRQ, CtriposShopInfoDO shopInfoDO, List<PriceAndQuotaDO> priceAndQuotaDOList) {

        Criterion criterion = otaHotelAvailRQ.getAvailRequestSegments().get(0).getHotelSearchCriteria().getCriterion();
        RoomStayCandidate roomStayCandidate = criterion.getRoomStayCandidates().get(0);
        HotelRef hotelRef = criterion.getHotelRef();

        CreateOrderRequestDTO request = new CreateOrderRequestDTO();
        request.setHotelId(Integer.valueOf(hotelRef.getHotelCode()));
        request.setChannelCode(shopInfoDO.getChannelCode());
        ChannelAgentDO channelAgentDO = InitData.channelAgentMap.get(shopInfoDO.getMerchantCode() + "-" + shopInfoDO.getChannelCode());
        request.setAgentCode(channelAgentDO.getAgentCode());
        request.setCreator(channelAgentDO.getAgentName());
        request.setMerchantCode(shopInfoDO.getMerchantCode());
        request.setIsGroupRoom(0);
        request.setIsManualOrder(0);

        // 房间人数
        HotelGuestCountDTO guestCountDTO = getAdultOrChildCount(roomStayCandidate.getGuestCounts());
        request.setAdultNum(guestCountDTO.getAdultCount() == null ? 2 : guestCountDTO.getAdultCount());
        request.setChildNum(guestCountDTO.getChildCount() == null ? 0 : guestCountDTO.getChildCount());

        // 产品明细
        CreateOrderProductRequestDTO orderProductRequestDTO = new CreateOrderProductRequestDTO();
        orderProductRequestDTO.setRoomTypeId(roomStayCandidate.getRoomTypeCode());
        orderProductRequestDTO.setRateplanId(Integer.valueOf(criterion.getRatePlanCandidates().get(0).getRatePlanCode()));
        orderProductRequestDTO.setCheckinDate(criterion.getStayDateRange().getStart());
        orderProductRequestDTO.setCheckoutDate(criterion.getStayDateRange().getEnd());
        orderProductRequestDTO.setRoomNum(roomStayCandidate.getQauantity());

        // 每日价格明细
        BigDecimal orderSum = BigDecimal.ZERO;
        List<SupplyProductPriceDTO> dayList = new ArrayList<>();
        for (PriceAndQuotaDO priceAndQuotaDO : priceAndQuotaDOList) {
            Date checkEnd = DateUtil.stringToDate(orderProductRequestDTO.getCheckoutDate());
            if (DateUtil.compare(priceAndQuotaDO.getSaleDate(), checkEnd) == 0) {
                continue;
            }
            SupplyProductPriceDTO priceDTO = new SupplyProductPriceDTO();
            priceDTO.setSalePrice(priceAndQuotaDO.getCtriposSalePrice());
            priceDTO.setSaleDate(DateUtil.dateToString(priceAndQuotaDO.getSaleDate()));
            dayList.add(priceDTO);
            orderSum = orderSum.add(priceAndQuotaDO.getCtriposSalePrice().multiply(new BigDecimal(orderProductRequestDTO.getRoomNum())));
        }
        orderProductRequestDTO.setDayList(dayList);
        request.setOrderSum(orderSum);
        List<CreateOrderProductRequestDTO> orderProductRequestDTOList = new ArrayList<>();
        orderProductRequestDTOList.add(orderProductRequestDTO);
        request.setRatePlanList(orderProductRequestDTOList);
        request.setCreateTime(new Date());
        return request;
    }

    /**
     * 组装试预定响应对象, 适用于有传价格计划的情况
     *
     * @param otaHotelAvailRS
     * @param ctripOsRatePlanMappingPO
     * @param preBookingResponseDTO
     * @param currencyKey
     * @param nights
     * @param quantity
     * @param adultCount
     * @param childCount
     */
    private void assemblyCheckAvailabilityForCommodityResponse(OTAHotelAvailRS otaHotelAvailRS, CtriposMapRateplanDO ctripOsRatePlanMappingPO,
                                                               ResponseDTO<Collection<SupplyProductPriceResponseDTO>> preBookingResponseDTO, Integer currencyKey, Long nights,
                                                               Integer quantity, Integer adultCount, Integer childCount) {
        List<RoomStay> roomStays = new ArrayList<>();

        int quotaNum = -1;
        Integer strictBreakfastNum = -2;
        Double totalAmount = 0d;
        List<Rate> rates = new ArrayList<>();
        Collection<SupplyProductPriceResponseDTO> productPriceResponseDTOS = preBookingResponseDTO.getModel();
        for (SupplyProductPriceResponseDTO productPriceDTO : productPriceResponseDTOS) {

            // 无底价||不可订||不及时确认||无售价||无房态||房态待查||满房||有房配额小于1
            String validateResult = CtripOSPreBookingUtil.validateForCommodityMode(productPriceDTO, ctripOsRatePlanMappingPO.getCommodityId().longValue());
            if (!"SUCCESS".equals(validateResult)) {
                log.debug("无底价||不可订||不及时确认||无售价||无房态||房态待查||满房||有房配额小于1, productPriceDTO=" + productPriceDTO);
                // 如果一天不可订，那么立即返回
                otaHotelAvailRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_3, CtripOSErrorEnum.CtripOSError_450, validateResult));
                return;
            }

            // 构建rate节点
            Rate rate = new Rate();
            rate.setEffectiveDate(productPriceDTO.getSaleDate());
            rate.setExpireDate(DateUtil.dateToString(DateUtil.getDate(DateUtil.stringToDate(productPriceDTO.getSaleDate()), 1, 0)));
            Base base = new Base();
            base.setAmountAfterTax(productPriceDTO.getSalePrice().toString());
            base.setAmountBeforeTax(productPriceDTO.getSalePrice().toString());
            base.setCurrencyCode(CurrencyEnum.getValueByKey(currencyKey));
            rate.setBase(base);
            rates.add(rate);

            // 取最小配额数
            quotaNum = (-1 == quotaNum) ? productPriceDTO.getQuotaNum() :
                    ((quotaNum < productPriceDTO.getQuotaNum()) ? quotaNum : productPriceDTO.getQuotaNum());
            totalAmount += Double.valueOf(base.getAmountAfterTax()) * quantity;

            //求最小早餐数（床位早特殊处理）
            if (productPriceDTO.getBreakFastType() == null) {
                strictBreakfastNum = -2;
            } else if (productPriceDTO.getBreakFastType() == -1) {
                strictBreakfastNum = Math.min(strictBreakfastNum, 2);
            } else {
                strictBreakfastNum = Math.min(strictBreakfastNum, productPriceDTO.getBreakFastType());
            }
        }

        RatePlanDescription ratePlanDescription = new RatePlanDescription();
        ratePlanDescription.setText(ctripOsRatePlanMappingPO.getCommodityNameEng());

        MealsIncluded mealsIncluded = new MealsIncluded();
        // 早餐
        if (-2 != strictBreakfastNum && 0 != strictBreakfastNum) {
            mealsIncluded.setBreakfast("true");
            if (-1 == strictBreakfastNum || strictBreakfastNum > 10) {
                //人头早按最大入住人数给
                mealsIncluded.setNumberOfBreakfast(2);
            } else {
                mealsIncluded.setNumberOfBreakfast(strictBreakfastNum);
            }
        } else {
            mealsIncluded.setBreakfast("false");
        }

        // 构建取消条款
        List<CancelPenalty> cancelPenalties = new ArrayList<CancelPenalty>();
        CancelPenalty cancelPenalty = new CancelPenalty();
        PenaltyDescription penaltyDescription = new PenaltyDescription();
        AmountPercent amountPercent = new AmountPercent();
        Deadline deadline = new Deadline();
        penaltyDescription.setText("一经预订，不可取消");
        // 设置最后取消时间为昨天
        deadline.setAbsoluteDeadline(calcCancelDeadline(new Date(), 1, null));
        amountPercent.setPercentage("100");
        cancelPenalty.setAmountPercent(amountPercent);
        cancelPenalty.setPenaltyDescription(penaltyDescription);
        cancelPenalty.setDeadline(deadline);
        cancelPenalties.add(cancelPenalty);

        // 构建价格计划RatePlan
        RatePlan ratePlan = new RatePlan();
        ratePlan.setRatePlanCode(String.valueOf(ctripOsRatePlanMappingPO.getCommodityId()));
        ratePlan.setMealsIncluded(mealsIncluded);
        ratePlan.setRatePlanDescription(ratePlanDescription);
        ratePlan.setCancelPenalties(cancelPenalties);
        ratePlan.setRatePlanDescription(ratePlanDescription);

        // 1.构建RatePlans节点
        List<RatePlan> ratePlans = new ArrayList<>();
        ratePlans.add(ratePlan);

        // 2.构建RoomTypes节点
        List<RoomType> roomTypes = new ArrayList<>();
        RoomType roomType = new RoomType();
        roomType.setRoomTypeCode(ctripOsRatePlanMappingPO.getRoomTypeId().toString());
        // 总件数
        roomType.setNumberOfUnits(quotaNum);
        roomTypes.add(roomType);

        // 3.构建RoomRates节点
        RoomRate roomRate = new RoomRate();
        roomRate.setRoomTypeCode(ctripOsRatePlanMappingPO.getRoomTypeId().toString());
        roomRate.setRatePlanCode(String.valueOf(ctripOsRatePlanMappingPO.getCommodityId()));
        roomRate.setRates(rates);

        // 构建Total总价节点
        Total total = new Total();
        //税后总价
        total.setAmountAfterTax(totalAmount);
        //税前总价，二者相等
        total.setAmountBeforeTax(totalAmount);
        //总价币种
        total.setCurrencyCode(CurrencyEnum.getValueByKey(currencyKey));
        roomRate.setTotal(total);
        List<RoomRate> roomRates = new ArrayList<RoomRate>();
        roomRates.add(roomRate);

        // 4.构建GuestCount节点
        List<GuestCount> gcs = buildGuestCount(adultCount, childCount);

        // 5.整合上述4个节点
        RoomStay roomStay = new RoomStay();
        roomStay.setRoomTypes(roomTypes);
        roomStay.setRatePlans(ratePlans);
        roomStay.setRoomRates(roomRates);
        roomStay.setGuestCounts(gcs);
        roomStays.add(roomStay);

        otaHotelAvailRS.setRoomStays(roomStays);
    }

    /**
     * 组装客人数量节点
     *
     * @param adultCount
     * @param childCount
     */
    private List<GuestCount> buildGuestCount(Integer adultCount, Integer childCount) {
        List<GuestCount> gcs = new ArrayList<>();
        if (null == adultCount && null == childCount) {
            GuestCount gc = new GuestCount();
            gc.setAgeQualifyingCode(10);
            gc.setCount(2);
            gcs.add(gc);
        } else {
            if (null != adultCount) {
                GuestCount gc = new GuestCount();
                gc.setAgeQualifyingCode(10);
                gc.setCount(adultCount);
                gcs.add(gc);
            }
            if (null != childCount) {
                GuestCount gc = new GuestCount();
                gc.setAgeQualifyingCode(8);
                gc.setCount(childCount);
                gcs.add(gc);
            }
        }
        return gcs;
    }

    /**
     * 组装试预定响应对象, 适用于没有传价格计划的情况
     *
     * @param otaHotelAvailRS
     * @param commodityDetailMap
     * @param roomNum
     * @param merchantCode
     * @param currencyKey
     * @param commodityMap
     * @param nights
     * @param endDate
     * @param adultCount
     * @param childCount
     */
    private void assemblyCheckAvailabilityForHotelResponse(OTAHotelAvailRS otaHotelAvailRS, Map<Long, List<PriceAndQuotaDO>> commodityDetailMap,
                                                           Integer roomNum, String merchantCode, Integer currencyKey,
                                                           Map<Long, CtriposMapRateplanDO> commodityMap, Long nights, Date endDate,
                                                           Integer adultCount, Integer childCount) {

        List<RoomStay> roomStays = new ArrayList<>();
        for (Map.Entry<Long, List<PriceAndQuotaDO>> commodityEntry : commodityDetailMap.entrySet()) {
            CtriposMapRateplanDO ctripOsRatePlanMappingPO = commodityMap.get(commodityEntry.getKey());

            //整个房型的最小配额数
            int quotaNum = -1;
            Date checkInDate = null;
            //价格计划的最小早餐数
            Integer strictBreakfastNum = -2;
            Double perCommodityTotalAmount = 0d;
            List<Rate> rates = new ArrayList<>();
            List<PriceAndQuotaDO> priceAndQuotaDOS = commodityEntry.getValue();
            for (PriceAndQuotaDO priceAndQuotaDO : priceAndQuotaDOS) {
                if (endDate.equals(priceAndQuotaDO.getSaleDate())) {
                    continue;
                }
                // 无底价||价格为待查||无加幅类型||无加幅||无售卖状态||无房态||房态待查||满房||有房配额小于1||预订条款||间数条款||连住大于1||限住||必住||配额不满足预订数||禁售
                if (!"SUCCESS".equals(CtripOSPreBookingUtil.validateForHotelMode(priceAndQuotaDO, priceAndQuotaDO.getPriceplanId().longValue()))
                        || priceAndQuotaDO.getQuotaNum() < roomNum) {
                    log.debug("无底价||价格为待查||无加幅类型||无加幅||无售卖状态||无房态||房态待查||满房||有房配额小于1||预订条款||间数条款||连住大于1||限住||必住||配额不满足预订数, detailDto="
                            + priceAndQuotaDO);
                    //有一天不通过，这个商品都还原，即不返回
                    rates.clear();
                    //有一天不通过，这个商品都还原，即不返回
                    perCommodityTotalAmount = 0d;
                    // 如果一天不可订，那么立即跳出循环，rates为空
                    break;
                }
                //取第一天，即入住日期
                if (null == checkInDate) {
                    checkInDate = priceAndQuotaDO.getSaleDate();
                }

                Rate rate = new Rate();
                rate.setEffectiveDate(DateUtil.dateToString(priceAndQuotaDO.getSaleDate()));
                rate.setExpireDate(DateUtil.dateToString(DateUtil.getDate(priceAndQuotaDO.getSaleDate(), 1, 0)));

                Base base = new Base();
                base.setAmountAfterTax(priceAndQuotaDO.getCtriposSalePrice().toString());
                base.setAmountBeforeTax(priceAndQuotaDO.getCtriposSalePrice().toString());
                base.setCurrencyCode(CurrencyEnum.getValueByKey(currencyKey));
                rate.setBase(base);
                rates.add(rate);

                //取最小配额数
                quotaNum = (-1 == quotaNum) ? priceAndQuotaDO.getQuotaNum() :
                        ((quotaNum < priceAndQuotaDO.getQuotaNum()) ? quotaNum : priceAndQuotaDO.getQuotaNum());

                perCommodityTotalAmount += Double.valueOf(base.getAmountAfterTax()) * roomNum;

                //求最小早餐数（床位早特殊处理）
                if (priceAndQuotaDO.getBreakFastType() == null) {
                    strictBreakfastNum = -2;
                } else if (priceAndQuotaDO.getBreakFastType() == -1) {
                    strictBreakfastNum = Math.min(strictBreakfastNum, 2);
                } else {
                    strictBreakfastNum = Math.min(strictBreakfastNum, priceAndQuotaDO.getBreakFastType());
                }
            }

            // 如果rate中有数据，表示这个商品这几天都是可订的，那么继续构造其他节点
            if (quotaNum > 0 && !CollectionUtils.isEmpty(rates)) {
                // 构建价格计划RatePlan
                RatePlan ratePlan = new RatePlan();
                ratePlan.setRatePlanCode(String.valueOf(ctripOsRatePlanMappingPO.getCommodityId()));
                RatePlanDescription ratePlanDescription = new RatePlanDescription();
                ratePlanDescription.setText(ctripOsRatePlanMappingPO.getCommodityNameEng());
                ratePlan.setRatePlanDescription(ratePlanDescription);

                // 构建mealsIncluded节点
                MealsIncluded mealsIncluded = new MealsIncluded();
                // 早餐,-2是默认值，0是无早
                if (-2 != strictBreakfastNum && 0 != strictBreakfastNum) {
                    mealsIncluded.setBreakfast("true");
                    if (-1 == strictBreakfastNum || strictBreakfastNum > 10) {
                        //人头早按最大入住人数给
                        mealsIncluded.setNumberOfBreakfast(2);
                    } else {
                        mealsIncluded.setNumberOfBreakfast(strictBreakfastNum);
                    }
                } else {
                    mealsIncluded.setBreakfast("false");
                }
                ratePlan.setMealsIncluded(mealsIncluded);

                // 构建取消条款
                CancelPenalty cancelPenalty = new CancelPenalty();
                PenaltyDescription penaltyDescription = new PenaltyDescription();
                AmountPercent amountPercent = new AmountPercent();
                amountPercent.setNmbrOfNights(nights.intValue());
                Deadline deadline = new Deadline();
                deadline.setAbsoluteDeadline(calcCancelDeadline(new Date(), 1, null));
                amountPercent.setPercentage("100");
                penaltyDescription.setText("一经预订，不可取消");
                cancelPenalty.setPenaltyDescription(penaltyDescription);
                cancelPenalty.setAmountPercent(amountPercent);
                cancelPenalty.setDeadline(deadline);
                ratePlan.getCancelPenalties().add(cancelPenalty);
                // 1.构建RatePlans节点
                List<RatePlan> ratePlans = new ArrayList<>();
                ratePlans.add(ratePlan);

                // 构建房价RoomRate
                RoomRate roomRate = new RoomRate();
                roomRate.setRoomTypeCode(ctripOsRatePlanMappingPO.getRoomTypeId().toString());
                roomRate.setRatePlanCode(commodityEntry.getKey().toString());
                roomRate.setRates(rates);
                // 构建Total总价节点
                Total total = new Total();
                total.setAmountAfterTax((null == total.getAmountAfterTax()) ? perCommodityTotalAmount : total.getAmountAfterTax() + perCommodityTotalAmount);
                total.setAmountBeforeTax(total.getAmountAfterTax());
                total.setCurrencyCode(CurrencyEnum.getValueByKey(currencyKey));
                roomRate.setTotal(total);

                // 2.构建RoomRates节点
                List<RoomRate> roomRates = new ArrayList<>();
                roomRates.add(roomRate);

                // 3.构建RoomTypes节点
                List<RoomType> roomTypes = new ArrayList<RoomType>();
                RoomType roomType = new RoomType();
                roomType.setRoomTypeCode(ctripOsRatePlanMappingPO.getRoomTypeId().toString());
                // 最小可订件数
                roomType.setNumberOfUnits(quotaNum);
                roomTypes.add(roomType);

                // 4.构建GuestCount节点
                List<GuestCount> gcs = buildGuestCount(adultCount, childCount);

                // 5.整合上述4个节点
                RoomStay roomStay = new RoomStay();
                roomStay.setRoomTypes(roomTypes);
                roomStay.setRatePlans(ratePlans);
                roomStay.setRoomRates(roomRates);
                roomStay.setGuestCounts(gcs);
                roomStays.add(roomStay);
            }
        }
        otaHotelAvailRS.setRoomStays(roomStays);
    }

    private String calcCancelDeadline(Date checkInDate, Integer cancelRestrictDays, String cancelRestrictTime) {
        String resultStr;
        Date d = DateUtil.getDate(checkInDate, 0 - cancelRestrictDays, 0);
        String dStr = DateUtil.dateToString(d, "yyyy-MM-dd");
        if (null == cancelRestrictTime) {
            resultStr = dStr + " 00:00:00";
        } else {
            if (cancelRestrictTime.startsWith("0")) {
                cancelRestrictTime = "0000";
            }
            //取小时
            String hour = cancelRestrictTime.substring(0, 2);
            resultStr = dStr + " " + hour + ":00:00";
        }
        resultStr = resultStr.replace(" ", "T");
        return resultStr;
    }

    /**
     * 校验试预定参数
     *
     * @param otaHotelAvailRQ
     * @param otaHotelAvailRS
     * @return
     */
    private CtriposShopInfoDO checkCheckAvailabilityParam(OTAHotelAvailRQ otaHotelAvailRQ, OTAHotelAvailRS otaHotelAvailRS) {

        // 1. 检验试预定参数信息
        if (null == otaHotelAvailRQ
                || null == otaHotelAvailRQ.getAvailRequestSegments()
                || null == otaHotelAvailRQ.getAvailRequestSegments().get(0)
                || null == otaHotelAvailRQ.getAvailRequestSegments().get(0).getHotelSearchCriteria()
                || null == otaHotelAvailRQ.getAvailRequestSegments().get(0).getHotelSearchCriteria().getCriterion()) {
            otaHotelAvailRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_10, CtripOSErrorEnum.CtripOSError_113, "Check availability param is empty"));
            return null;
        }
        Criterion criterion = otaHotelAvailRQ.getAvailRequestSegments().get(0).getHotelSearchCriteria().getCriterion();
        if (null == criterion.getHotelRef()
                || null == criterion.getHotelRef().getHotelCode()
                || null == criterion.getStayDateRange()
                || null == criterion.getStayDateRange().getStart()
                || null == criterion.getStayDateRange().getEnd()
                || null == criterion.getRoomStayCandidates()) {
            otaHotelAvailRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_10, CtripOSErrorEnum.CtripOSError_113, "Check availability param is empty"));
            return null;
        }
        List<RoomStayCandidate> roomStayCandidates = criterion.getRoomStayCandidates();
        if (null == roomStayCandidates
                || null == roomStayCandidates.get(0)
                || null == roomStayCandidates.get(0).getQauantity()
                || null == roomStayCandidates.get(0).getGuestCounts()
                || null == roomStayCandidates.get(0).getGuestCounts().get(0).getAgeQualifyingCode()) {
            otaHotelAvailRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_10, CtripOSErrorEnum.CtripOSError_113, "Check availability param is empty"));
            return null;
        }
        // 验证客人数量
        HotelGuestCountDTO guestCountDTO = getAdultOrChildCount(roomStayCandidates.get(0).getGuestCounts());
        Integer adultCount = guestCountDTO.getAdultCount() == null ? 0 : guestCountDTO.getAdultCount();
        Integer childCount = guestCountDTO.getChildCount() == null ? 0 : guestCountDTO.getChildCount();
        if (adultCount + childCount > 2) {
            otaHotelAvailRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_3, CtripOSErrorEnum.CtripOSError_450, "Person count is wrong"));
            return null;
        }

        // 2. 检验接口认证账号信息
        return getCommonCtripOSAuthConfig(otaHotelAvailRQ.getPos(), otaHotelAvailRS);
    }

    /**
     * 检验接口认证账号信息公共方法
     *
     * @param pos
     * @param rsHeader
     * @return
     */
    private CtriposShopInfoDO getCommonCtripOSAuthConfig(Pos pos, RSHeader rsHeader) {
        // 检验用户名、密码信息
        if (null == pos || null == pos.getSource().getRequestorId() || null == pos.getSource().getRequestorId().getId()
                || null == pos.getSource().getRequestorId().getMessagePassword()) {
            rsHeader.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_4, CtripOSErrorEnum.CtripOSError_321, "Auth info cannot be empty"));
            return null;
        }
        // 检验商家配置信息
        RequestorID requestorId = pos.getSource().getRequestorId();
        CtriposShopInfoDO shopInfoDO = InitData.userPwdShopMap.get(requestorId.getId() + "-" + requestorId.getMessagePassword());
        if (null == shopInfoDO) {
            rsHeader.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_4, CtripOSErrorEnum.CtripOSError_321, "ID and password do not match"));
            return null;
        }
        return shopInfoDO;
    }

    @Override
    public OTAHotelResRS createReservation(OTAHotelResRQ otaHotelResRQ) {
        OTAHotelResRS otaHotelResRS = new OTAHotelResRS();
        otaHotelResRS.setEchoToken(CtripOSCommonUtil.buildDefaultToken());
        otaHotelResRS.setVersion(CtripOSCommonConstants.CTRIP_OS_IF_VERSION.toString());
        Date start = new Date();

        try {
            // 1. 检验参数
            CtriposShopInfoDO shopInfoDO = checkCreateReservationParam(otaHotelResRQ, otaHotelResRS);
            if (!CollectionUtils.isEmpty(otaHotelResRS.getErrors())) {
                return otaHotelResRS;
            }
            // 2. 检查订单是否重复,如果已存在直接返回
            OrderDO orderQuery = new OrderDO();
            String ctripOrderId = otaHotelResRQ.getUniqueID().getId();
            orderQuery.setCustomerOrderCode(ctripOrderId);
            orderQuery.setChannelCode(shopInfoDO.getChannelCode());
            List<OrderDO> orderDOS = orderMapper.select(orderQuery);
            if (!CollectionUtils.isEmpty(orderDOS)) {
                List<Warning> warnings = new ArrayList<>();
                Warning warning = new Warning();
                warning.setType(CtripOSTypeEnum.CtripOSType_3.getType());
                warning.setCode(CtripOSErrorEnum.CtripOSError_127.getCode());
                warning.setWarningText("This creation is already successfully processed");
                warnings.add(warning);
                otaHotelResRS.setWarnings(warnings);
                assemblyCreateReservationResponse(otaHotelResRS, ctripOrderId, orderDOS.get(0).getOrderCode(), orderDOS.get(0).getOrderStatus().intValue());
                return otaHotelResRS;
            }
            // 3. 组装下单参数
            CreateOrderRequestDTO createOrderRequestDTO = buildCreateOrderRequestDTO(otaHotelResRQ, shopInfoDO);
            // 4. 下单
            ResponseDTO<Integer> createOrderResponse = OrderInterfaceInvoker.addManualOrder(orderCallConfig, JSON.toJSONString(createOrderRequestDTO));
            if (null == createOrderResponse || createOrderResponse.getResult() != 1) {
                otaHotelResRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_3, CtripOSErrorEnum.CtripOSError_111,
                        null == createOrderResponse ? FCerrorEnum.FC_ERROR_5.getName() : createOrderResponse.getFailReason()));
                assemblyCreateReservationResponse(otaHotelResRS, otaHotelResRQ.getUniqueID().getId(), null, null);
                log.error("下单失败，携程订单id: {}, 响应：{}", ctripOrderId, JSON.toJSONString(createOrderResponse));
                return otaHotelResRS;
            }
            // 5. 组装响应参数
            OrderDO orderDO = orderMapper.selectByPrimaryKey(createOrderResponse.getModel());
            assemblyCreateReservationResponse(otaHotelResRS, ctripOrderId, orderDO.getOrderCode(), OrderStatusEnum.NEWORDER.key);
        } catch (Exception e) {
            otaHotelResRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_3, CtripOSErrorEnum.CtripOSError_448, FCerrorEnum.FC_ERROR_5.getName()));
            assemblyCreateReservationResponse(otaHotelResRS, otaHotelResRQ.getUniqueID().getId(), null, null);
            log.error("createReservation exception.", e);
        } finally {
            // 6. 记日志
            log.info("createReservation request:{},response:{}", JAXBUtil.jaxbBeanToXml(otaHotelResRQ), JAXBUtil.jaxbBeanToXml(otaHotelResRS));
        }

        if (CollectionUtils.isEmpty(otaHotelResRS.getErrors())) {
            otaHotelResRS.setSuccess("");
        }
        return otaHotelResRS;
    }

    /**
     * 组装创建订单请求参数
     *
     * @param otaHotelResRQ
     * @param shopInfoDO
     * @return
     */
    private CreateOrderRequestDTO buildCreateOrderRequestDTO(OTAHotelResRQ otaHotelResRQ, CtriposShopInfoDO shopInfoDO) {
        HotelReservation hotelReservation = otaHotelResRQ.getHotelReservations().get(0);
        RoomStay roomStay = hotelReservation.getRoomStays().get(0);
        ResGlobalInfo resGlobalInfo = hotelReservation.getResGlobalInfo();

        CreateOrderRequestDTO request = new CreateOrderRequestDTO();
        request.setHotelId(Integer.valueOf(roomStay.getBasicPropertyInfo().getHotelCode()));
        request.setChannelCode(shopInfoDO.getChannelCode());
        request.setCustomerOrderCode(String.valueOf(otaHotelResRQ.getUniqueID().getId()));
        request.setOrderSum(new BigDecimal(resGlobalInfo.getTotal().getAmountAfterTax()));
        ChannelAgentDO channelAgentDO = InitData.channelAgentMap.get(shopInfoDO.getMerchantCode() + "-" + shopInfoDO.getChannelCode());
        request.setAgentCode(channelAgentDO.getAgentCode());
        request.setCreator(channelAgentDO.getAgentName());
        request.setMerchantCode(shopInfoDO.getMerchantCode());
        request.setIsGroupRoom(0);
        request.setIsManualOrder(0);

        // 联系人
        Customer customer = hotelReservation.getResGuests().get(0).getProfiles().get(0).getProfile().getCustomer();
        ContactPerson contactPerson = customer.getContactPerson();
        if (null != contactPerson) {
            request.setContractName(StringUtil.isValidString(contactPerson.getPersonName().getGivenName()) ? contactPerson.getPersonName().getGivenName() : "CTRIP-OS");
            request.setContractPhone(StringUtil.isValidString(contactPerson.getTelephone().getPhoneNumber()) ? contactPerson.getTelephone().getPhoneNumber() : "66666666");
        }

        // 特殊要求
        List<SpecialRequest> specialList = resGlobalInfo.getSpecialRequests();
        StringBuffer sb = new StringBuffer();
        if (null != specialList && specialList.size() > 0) {
            sb.append("(1)" + specialList.get(0).getText().getText() + "\n");
            int num = 1;
            for (ListItem listItem : specialList.get(0).getItemLists()) {
                num++;
                sb.append("(" + num + ")" + listItem.getText() + "\n");
            }
        }
        request.setSpecialRequest(sb.toString());

        // 入住人信息
        List<String> guests = new ArrayList<>();
        for (PersonName personName : customer.getPersonNames()) {
            guests.add(personName.getSurname() + "/" + personName.getGivenName());
        }
        request.setGuestList(guests);

        // 房间人数
        HotelGuestCountDTO guestCountDTO = getAdultOrChildCount(resGlobalInfo.getGuestCounts());
        request.setAdultNum(guestCountDTO.getAdultCount() == null ? 2 : guestCountDTO.getAdultCount());
        request.setChildNum(guestCountDTO.getChildCount() == null ? 0 : guestCountDTO.getChildCount());

        // 产品明细
        CreateOrderProductRequestDTO orderProductRequestDTO = new CreateOrderProductRequestDTO();
        orderProductRequestDTO.setRoomTypeId(roomStay.getRoomTypes().get(0).getRoomTypeCode());
        orderProductRequestDTO.setRateplanId(Integer.valueOf(roomStay.getRatePlans().get(0).getRatePlanCode()));
        Date start = DateUtil.stringToDate(hotelReservation.getResGlobalInfo().getTimeSpan().getStart(), "yyyy-MM-dd HH:mm:ss");
        Date end = DateUtil.stringToDate(hotelReservation.getResGlobalInfo().getTimeSpan().getEnd(), "yyyy-MM-dd HH:mm:ss");
        orderProductRequestDTO.setCheckinDate(DateUtil.dateToString(start));
        orderProductRequestDTO.setCheckoutDate(DateUtil.dateToString(end));
        orderProductRequestDTO.setRoomNum(Integer.valueOf(roomStay.getRoomRates().get(0).getNumberOfUnits()));

        // 每日价格明细
        List<SupplyProductPriceDTO> dayList = new ArrayList<>();
        List<Rate> rateList = roomStay.getRoomRates().get(0).getRates();
        for (Rate rate : rateList) {
            Date startDate = DateUtil.stringToDate(rate.getEffectiveDate(), "yyyy-MM-dd");
            Date endDate = DateUtil.stringToDate(rate.getExpireDate(), "yyyy-MM-dd");
            long num = DateUtil.getDay(startDate, endDate);
            for (int i = 0; i < num; i++) {
                SupplyProductPriceDTO priceDTO = new SupplyProductPriceDTO();
                priceDTO.setSalePrice(new BigDecimal(rate.getBase().getAmountAfterTax()));
                priceDTO.setSaleDate(DateUtil.dateToString(DateUtil.getDate(startDate, i, 0)));
                dayList.add(priceDTO);
            }
        }
        orderProductRequestDTO.setDayList(dayList);
        List<CreateOrderProductRequestDTO> orderProductRequestDTOList = new ArrayList<>();
        orderProductRequestDTOList.add(orderProductRequestDTO);
        request.setRatePlanList(orderProductRequestDTOList);
        request.setCreateTime(new Date());
        // 问题单
        request.setCanBeExceptionOrder(1);
        return request;
    }

    /**
     * 组装下单公共响应对象
     *
     * @param otaHotelResRS
     * @param ctripOrderId
     * @param orderCode
     * @param fcOrderState
     */
    private void assemblyCreateReservationResponse(OTAHotelResRS otaHotelResRS, String ctripOrderId, String orderCode, Integer fcOrderState) {
        List<HotelReservation> hotelReservations = new ArrayList<>();
        HotelReservation hotelReservation = new HotelReservation();
        String resStatus;
        if (null == fcOrderState) {
            resStatus = "R";
        } else if (fcOrderState.equals(OrderStatusEnum.TRADED.key)) {
            resStatus = "S";
        } else if (fcOrderState.equals(OrderStatusEnum.CANCELED.key)) {
            resStatus = "C";
        } else if (fcOrderState.equals(OrderStatusEnum.NEWORDER.key)
                || fcOrderState.equals(OrderStatusEnum.PROCESSING.key)) {
            resStatus = "P";
        } else {
            resStatus = "R";
        }
        hotelReservation.setResStatus(resStatus);
        ResGlobalInfo resGlobalInfo = new ResGlobalInfo();
        List<HotelReservationID> hotelReservationIDs = new ArrayList<>();
        HotelReservationID ctripHotelReservationID = new HotelReservationID();
        ctripHotelReservationID.setResID_Type("501");
        ctripHotelReservationID.setResID_Value(ctripOrderId);
        hotelReservationIDs.add(ctripHotelReservationID);
        if (!StringUtils.isEmpty(orderCode)) {
            HotelReservationID fcHotelReservationID = new HotelReservationID();
            fcHotelReservationID.setResID_Type("502");
            fcHotelReservationID.setResID_Value(orderCode);
            hotelReservationIDs.add(fcHotelReservationID);
        }
        resGlobalInfo.setHotelReservationIDs(hotelReservationIDs);
        hotelReservation.setResGlobalInfo(resGlobalInfo);
        hotelReservations.add(hotelReservation);
        otaHotelResRS.setHotelReservations(hotelReservations);
    }

    /**
     * 校验下单参数
     *
     * @param otaHotelResRQ
     * @param otaHotelResRS
     */
    private CtriposShopInfoDO checkCreateReservationParam(OTAHotelResRQ otaHotelResRQ, OTAHotelResRS otaHotelResRS) {
        // 1. 检验参数信息
        if (null == otaHotelResRQ
                || null == otaHotelResRQ.getUniqueID()
                || null == otaHotelResRQ.getUniqueID().getId()
                || null == otaHotelResRQ.getHotelReservations()
                || null == otaHotelResRQ.getHotelReservations().get(0).getRoomStays()
                || null == otaHotelResRQ.getHotelReservations().get(0).getResGuests()
                || null == otaHotelResRQ.getHotelReservations().get(0).getResGlobalInfo()) {
            otaHotelResRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_10, CtripOSErrorEnum.CtripOSError_113, FCerrorEnum.FC_ERROR_7.getName()));
            return null;
        }
        RoomStay roomStay = otaHotelResRQ.getHotelReservations().get(0).getRoomStays().get(0);
        if (null == roomStay
                || null == roomStay.getBasicPropertyInfo()
                || null == roomStay.getBasicPropertyInfo().getHotelCode()
                || null == roomStay.getRoomTypes()
                || null == roomStay.getRoomTypes().get(0)
                || null == roomStay.getRoomTypes().get(0).getRoomTypeCode()
                || null == roomStay.getRatePlans()
                || null == roomStay.getRatePlans().get(0)
                || null == roomStay.getRatePlans().get(0).getRatePlanCode()
                || null == roomStay.getRoomRates()
                || null == roomStay.getRoomRates().get(0).getNumberOfUnits()
                || null == roomStay.getRoomRates().get(0).getRates()) {
            otaHotelResRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_10, CtripOSErrorEnum.CtripOSError_113, "HotelCode|RoomTypeCode|RatePlanCode cannot be empty"));
            return null;
        }
        // 校验商品是否有效
        List<Long> pricePlanIdList = new ArrayList<>();
        pricePlanIdList.add(Long.valueOf(roomStay.getRatePlans().get(0).getRatePlanCode()));
        List<PricePlanDO> pricePlanDOS = pricePlanService.queryByIds(pricePlanIdList);
        if (CollectionUtils.isEmpty(pricePlanDOS)
                || !pricePlanDOS.get(0).getHotelId().toString().equals(roomStay.getBasicPropertyInfo().getHotelCode())
                || !pricePlanDOS.get(0).getRoomTypeId().toString().equals(roomStay.getRoomTypes().get(0).getRoomTypeCode())) {
            otaHotelResRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_3, CtripOSErrorEnum.CtripOSError_400, "HotelCode|RoomTypeCode|RatePlanCode is not a valid property code"));
            return null;
        }

        // 校验该商品是不是预付
        if (null == roomStay.getRoomRates().get(0).getRatePlanCategory()
                || !"501".equals(roomStay.getRoomRates().get(0).getRatePlanCategory())) {
            otaHotelResRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_10, CtripOSErrorEnum.CtripOSError_400, FCerrorEnum.FC_ERROR_47.getName()));
            return null;
        }
        // 验证价格明细
        List<Rate> rates = roomStay.getRoomRates().get(0).getRates();
        for (Rate rate : rates) {
            if (null == rate
                    || null == rate.getEffectiveDate()
                    || null == rate.getExpireDate()
                    || null == rate.getBase().getAmountAfterTax()
                    || null == rate.getBase().getCurrencyCode()) {
                otaHotelResRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_10, CtripOSErrorEnum.CtripOSError_113, "Rate missing required attributes"));
                return null;
            }
        }
        // 验证客人、联系人信息
        ResGuest resGuest = otaHotelResRQ.getHotelReservations().get(0).getResGuests().get(0);
        if (null == resGuest
                || null == resGuest.getProfiles()
                || null == resGuest.getProfiles().get(0)
                || null == resGuest.getProfiles().get(0).getProfile()
                || null == resGuest.getProfiles().get(0).getProfile().getCustomer().getContactPerson().getPersonName().getGivenName()
                || null == resGuest.getProfiles().get(0).getProfile().getCustomer().getContactPerson().getPersonName().getSurname()
                || null == resGuest.getProfiles().get(0).getProfile().getCustomer().getPersonNames()) {
            otaHotelResRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_10, CtripOSErrorEnum.CtripOSError_113, "ResGuest missing required attributes"));
            return null;
        }
        List<PersonName> personNames = resGuest.getProfiles().get(0).getProfile().getCustomer().getPersonNames();
        for (PersonName personName : personNames) {
            if (null == personName
                    || null == personName.getGivenName()
                    || null == personName.getSurname()) {
                otaHotelResRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_10, CtripOSErrorEnum.CtripOSError_113, "PersonName missing required attributes"));
                return null;
            }
        }
        // 验证订单全局参数
        ResGlobalInfo resGlobalInfo = otaHotelResRQ.getHotelReservations().get(0).getResGlobalInfo();
        if (null == resGlobalInfo
                || null == resGlobalInfo.getGuestCounts()
                || null == resGlobalInfo.getTimeSpan()
                || null == resGlobalInfo.getTimeSpan().getStart()
                || null == resGlobalInfo.getTimeSpan().getEnd()
                || null == resGlobalInfo.getTotal()
                || null == resGlobalInfo.getTotal().getAmountAfterTax()
                || null == resGlobalInfo.getTotal().getCurrencyCode()
                || null == resGlobalInfo.getHotelReservationIDs()) {
            otaHotelResRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_10, CtripOSErrorEnum.CtripOSError_113, "ResGlobalInfo missing required attributes"));
            return null;
        }
        // 验证入离日期
        Date start = DateUtil.stringToDate(resGlobalInfo.getTimeSpan().getStart(), "yyyy-MM-dd HH:mm:ss");
        Date end = DateUtil.stringToDate(resGlobalInfo.getTimeSpan().getEnd(), "yyyy-MM-dd HH:mm:ss");
        if (DateUtil.compare(start, end) >= 0) {
            otaHotelResRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_10, CtripOSErrorEnum.CtripOSError_382, FCerrorEnum.FC_ERROR_30.getName()));
            return null;
        }
        // 验证总价与各明细之和
        BigDecimal priceSum = BigDecimal.ZERO;
        String currency = resGlobalInfo.getTotal().getCurrencyCode();
        for (Rate rate : roomStay.getRoomRates().get(0).getRates()) {
            Date startDate = DateUtil.stringToDate(rate.getEffectiveDate());
            Date endDate = DateUtil.stringToDate(rate.getExpireDate());
            long num = DateUtil.getDay(startDate, endDate);
            priceSum = priceSum.add(new BigDecimal(rate.getBase().getAmountAfterTax()).multiply(BigDecimal.valueOf(num)));
            if (!currency.equals(rate.getBase().getCurrencyCode())) {
                otaHotelResRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_3, CtripOSErrorEnum.CtripOSError_61, FCerrorEnum.FC_ERROR_42.getName()));
                return null;
            }
        }
        String roomNum = roomStay.getRoomRates().get(0).getNumberOfUnits();
        priceSum = priceSum.multiply(new BigDecimal(roomNum));
        if (priceSum.compareTo(BigDecimal.valueOf(resGlobalInfo.getTotal().getAmountAfterTax())) != 0) {
            otaHotelResRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_3, CtripOSErrorEnum.CtripOSError_400, FCerrorEnum.FC_ERROR_33.getName()));
            return null;
        }
        // 验证携程订单号
        for (HotelReservationID hotelReservationID : resGlobalInfo.getHotelReservationIDs()) {
            if (null == hotelReservationID
                    || null == hotelReservationID.getResID_Type()
                    || null == hotelReservationID.getResID_Value()) {
                otaHotelResRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_10, CtripOSErrorEnum.CtripOSError_113, "HotelReservationID missing required attributes"));
                return null;
            }
        }
        // 验证下单人数
        HotelGuestCountDTO guestCountDTO = getAdultOrChildCount(resGlobalInfo.getGuestCounts());
        Integer adultCount = guestCountDTO.getAdultCount() == null ? 0 : guestCountDTO.getAdultCount();
        Integer childCount = guestCountDTO.getChildCount() == null ? 0 : guestCountDTO.getChildCount();

        if ((adultCount + childCount > (2 * Integer.valueOf(roomNum))) || (adultCount + childCount == 0)) {
            otaHotelResRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_3, CtripOSErrorEnum.CtripOSError_450, "Person count is wrong"));
            return null;
        }

        // 2. 检验接口认证账号信息
        return getCommonCtripOSAuthConfig(otaHotelResRQ.getPos(), otaHotelResRS);
    }


    @Override
    public OTACancelRS cancelReservation(OTACancelRQ otaCancelRQ) {
        OTACancelRS otaCancelRS = new OTACancelRS();
        otaCancelRS.setEchoToken(CtripOSCommonUtil.buildDefaultToken());
        otaCancelRS.setVersion(CtripOSCommonConstants.CTRIP_OS_IF_VERSION.toString());
        Date start = new Date();

        try {
            // 1. 校验参数
            CtriposShopInfoDO shopInfoDO = checkCancelReservationParam(otaCancelRQ, otaCancelRS);
            if (!CollectionUtils.isEmpty(otaCancelRS.getErrors())) {
                return otaCancelRS;
            }
            // 2. 组装取消订单请求参数
            String fcOrderCode = null;
            String ctripOrderCode = null;
            for (UniqueID uniqueID : otaCancelRQ.getUniqueIDs()) {
                if (uniqueID.getType().equals(UniqueIDEnum.UNIQUEID_501.key)) {
                    ctripOrderCode = uniqueID.getId();
                }
                if (uniqueID.getType().equals(UniqueIDEnum.UNIQUEID_502.key)) {
                    fcOrderCode = uniqueID.getId();
                }
            }

            // 3. 查询订单详情
            OrderDO orderQuery = new OrderDO();
            orderQuery.setOrderCode(fcOrderCode);
            List<OrderDO> orderDOS = orderMapper.select(orderQuery);
            if (CollectionUtils.isEmpty(orderDOS)) {
                otaCancelRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_3, CtripOSErrorEnum.CtripOSError_245, "Reservation(502) " + fcOrderCode + " cannot be found"));
                assemblyCancelReservationResponse(otaCancelRS, ctripOrderCode, fcOrderCode, null);
                return otaCancelRS;
            }
            OrderDO orderDO = orderDOS.get(0);
            if (orderDO.getOrderStatus().intValue() == OrderStatusEnum.CANCELED.key) {
                // 如果订单已取消，需要给出警告信息
                List<Warning> warnings = new ArrayList<>();
                Warning warning = new Warning();
                warning.setType(CtripOSTypeEnum.CtripOSType_3.getType());
                warning.setCode(CtripOSErrorEnum.CtripOSError_95.getCode());
                warning.setWarningText("The reservation has been cancelled already");
                warnings.add(warning);
                otaCancelRS.setWarnings(warnings);
                assemblyCancelReservationResponse(otaCancelRS, ctripOrderCode, fcOrderCode, null);
                otaCancelRS.setSuccess("");
                return otaCancelRS;
            }

            // 4. 添加取消订单申请
            OrderRequestDTO addOrderReqRequestDTO = new OrderRequestDTO();
            addOrderReqRequestDTO.setOrderId(orderDO.getId());
            addOrderReqRequestDTO.setRequestType(Byte.valueOf("1"));
            addOrderReqRequestDTO.setCreator("system");
            ResponseDTO orderResponse = OrderInterfaceInvoker.addOrderRequest(orderCallConfig, JSON.toJSONString(addOrderReqRequestDTO));
            if (null == orderResponse) {
                otaCancelRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_3, CtripOSErrorEnum.CtripOSError_245, "Reservation(501) " + ctripOrderCode + " cannot be found"));
                assemblyCancelReservationResponse(otaCancelRS, ctripOrderCode, fcOrderCode, null);
                return otaCancelRS;
            }
            // 4. 组装响应参数
            assemblyCancelReservationResponse(otaCancelRS, ctripOrderCode, fcOrderCode, orderResponse);
        } catch (Exception e) {
            otaCancelRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_3, CtripOSErrorEnum.CtripOSError_448, FCerrorEnum.FC_ERROR_6.getName()));
            log.error("cancelReservation exception.", e);
        } finally {
            // 5. 记日志
            log.info("cancelReservation request:{},response:{}", JAXBUtil.jaxbBeanToXml(otaCancelRQ), JAXBUtil.jaxbBeanToXml(otaCancelRS));
        }

        if (CollectionUtils.isEmpty(otaCancelRS.getErrors())) {
            otaCancelRS.setSuccess("");
        }
        return otaCancelRS;
    }

    /**
     * 组装取消订单响应对象
     *
     * @param otaCancelRS
     * @param ctripOrderCode
     * @param fcOrderCode
     * @param orderResponse
     */
    private void assemblyCancelReservationResponse(OTACancelRS otaCancelRS, String ctripOrderCode, String fcOrderCode, ResponseDTO orderResponse) {
        // 携程订单
        List<UniqueID> uniqueIDs = new ArrayList<>();
        UniqueID ctripUniqueID = new UniqueID();
        ctripUniqueID.setType("501");
        ctripUniqueID.setId(ctripOrderCode);
        uniqueIDs.add(ctripUniqueID);
        // 房仓订单
        UniqueID fcUniqueID = new UniqueID();
        fcUniqueID.setType("502");
        fcUniqueID.setId(fcOrderCode);
        uniqueIDs.add(fcUniqueID);
        otaCancelRS.setUniqueIDs(uniqueIDs);

        if (null == orderResponse) {
            return;
        }
        if (orderResponse.getResult() == 1) {
            // 如果订单需要再次确认，需要给出警告信息
            List<Warning> warnings = new ArrayList<>();
            Warning warning = new Warning();
            warning.setType(CtripOSTypeEnum.CtripOSType_3.getType());
            warning.setWarningText("May need to double confirm with hotel");
            warnings.add(warning);
            otaCancelRS.setWarnings(warnings);
        } else {
            // 取消订单失败，需要给出错误信息
            List<Error> errors = new ArrayList<>();
            Error error = new Error();
            error.setType(CtripOSTypeEnum.CtripOSType_3.getType());
            error.setCode(CtripOSErrorEnum.CtripOSError_450.getCode());
            error.setShortText("Cancel order failed! code: " + orderResponse.getFailReason());
            errors.add(error);
            otaCancelRS.setErrors(errors);
        }
    }

    /**
     * 校验取消订单参数
     *
     * @param otaCancelRQ
     * @param otaCancelRS
     * @return
     */
    private CtriposShopInfoDO checkCancelReservationParam(OTACancelRQ otaCancelRQ, OTACancelRS otaCancelRS) {
        // 1. 检验参数信息
        if (null == otaCancelRQ
                || CollectionUtils.isEmpty(otaCancelRQ.getUniqueIDs())) {
            otaCancelRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_10, CtripOSErrorEnum.CtripOSError_315, "Cancel reservation param is empty"));
            return null;
        }
        for (UniqueID uniqueID : otaCancelRQ.getUniqueIDs()) {
            if (null == uniqueID || null == uniqueID.getType() || null == uniqueID.getId()) {
                otaCancelRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_10, CtripOSErrorEnum.CtripOSError_315, "Cancel reservation param is empty"));
                return null;
            }
        }

        // 2. 检验接口认证账号信息
        return getCommonCtripOSAuthConfig(otaCancelRQ.getPos(), otaCancelRS);
    }

    @Override
    public OTAReadRS readReservation(OTAReadRQ otaReadRQ) {
        OTAReadRS otaReadRS = new OTAReadRS();
        otaReadRS.setEchoToken(CtripOSCommonUtil.buildDefaultToken());
        otaReadRS.setVersion(CtripOSCommonConstants.CTRIP_OS_IF_VERSION.toString());
        Date start = new Date();

        try {
            // 1. 校验参数
            CtriposShopInfoDO shopInfoDO = checkReadReservationParam(otaReadRQ, otaReadRS);
            if (!CollectionUtils.isEmpty(otaReadRS.getErrors())) {
                return otaReadRS;
            }
            // 2. 组装查询订单请求参数
            Map<String, String> readReservationMap = new HashMap<>();
            List<ReadRequest> readRequests = otaReadRQ.getReadRequests();
            for (ReadRequest readRequest : readRequests) {
                String fcOrderCode = null;
                String ctripOrderCode = null;
                for (UniqueID uniqueID : readRequest.getUniqueIDs()) {
                    if (uniqueID.getType().equals(UniqueIDEnum.UNIQUEID_501.key)) {
                        ctripOrderCode = uniqueID.getId();
                    }
                    if (uniqueID.getType().equals(UniqueIDEnum.UNIQUEID_502.key)) {
                        fcOrderCode = uniqueID.getId();
                    }
                }
                if (fcOrderCode != null && ctripOrderCode != null) {
                    readReservationMap.put(fcOrderCode, ctripOrderCode);
                }
            }
            // 3. 查询订单
            Example example = new Example(OrderDO.class);
            Example.Criteria criteria = example.createCriteria();
            criteria.andIn("orderCode", readReservationMap.keySet());
            List<OrderDO> orderDOS = orderMapper.selectByExample(example);
            if (CollectionUtils.isEmpty(orderDOS)) {
                otaReadRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_3, CtripOSErrorEnum.CtripOSError_245, "All reservation is not found"));
                assemblyReadReservationResponse(otaReadRS, null, readReservationMap);
                return otaReadRS;
            }
            // 4. 组装响应参数
            assemblyReadReservationResponse(otaReadRS, orderDOS, readReservationMap);

        } catch (Exception e) {
            otaReadRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_3, CtripOSErrorEnum.CtripOSError_448, FCerrorEnum.FC_ERROR_49.getName()));
            log.error("readReservation exception.", e);
        } finally {
            // 5. 记日志
            log.info("readReservation request:{},response:{}", JAXBUtil.jaxbBeanToXml(otaReadRQ), JAXBUtil.jaxbBeanToXml(otaReadRS));
        }

        if (CollectionUtils.isEmpty(otaReadRS.getErrors())) {
            otaReadRS.setSuccess("");
        }
        return otaReadRS;
    }

    @Override
    public String orderRequestEntry(String xmlReq, String requestType) {
        if (null == requestType) {
            return null;
        }
        String responseXML = null;
        try {
            if (CtripOSCommonConstants.OTA_HotelAvailRQ.equals(requestType)) {
                OTAHotelAvailRQ request = JAXBUtil.jaxbXMLToBean(OTAHotelAvailRQ.class, xmlReq);
                OTAHotelAvailRS response = checkAvailability(request);
                responseXML = JAXBUtil.jaxbBeanToXml(response);
            } else if (CtripOSCommonConstants.OTA_HotelResRQ.equals(requestType)) {
                OTAHotelResRQ request = JAXBUtil.jaxbXMLToBean(OTAHotelResRQ.class, xmlReq);
                OTAHotelResRS response = createReservation(request);
                responseXML = JAXBUtil.jaxbBeanToXml(response);
            } else if (CtripOSCommonConstants.OTA_CancelRQ.equals(requestType)) {
                OTACancelRQ request = JAXBUtil.jaxbXMLToBean(OTACancelRQ.class, xmlReq);
                OTACancelRS response = cancelReservation(request);
                responseXML = JAXBUtil.jaxbBeanToXml(response);
            } else if (CtripOSCommonConstants.OTA_ReadRQ.equals(requestType)) {
                OTAReadRQ request = JAXBUtil.jaxbXMLToBean(OTAReadRQ.class, xmlReq);
                OTAReadRS response = readReservation(request);
                responseXML = JAXBUtil.jaxbBeanToXml(response);
            }
        } catch (Exception e) {
            log.error("Order operate exception, requestXML: {}", xmlReq, e);
            RSHeader response = new RSHeader();
            response.setTimeStamp(CtripOSCommonUtil.dateToXMLGregorianCalendar(new Date()));
            response.setPrimaryLangID(CtripOSCommonConstants.CTRIP_OS_PRIMARY_LANG_ID_EN);
            response.setEchoToken(CtripOSCommonUtil.buildDefaultToken());
            response.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_12, CtripOSErrorEnum.CtripOSError_448, FCerrorEnum.FC_ERROR_0.getName()));
            responseXML = JAXBUtil.jaxbBeanToXml(response);
        }
        return responseXML;
    }

    @Override
    public ResponseDTO pushConfirmNo(PushConfirmNoRequest request) {
        ResponseDTO responseDTO = new ResponseDTO();
        if (StringUtils.isEmpty(request.getConfirmNo())
                || StringUtils.isEmpty(request.getOrderCode())
                || StringUtils.isEmpty(request.getCtripOrderCode())
                || StringUtils.isEmpty(request.getMerchantCode())
                || StringUtils.isEmpty(request.getShopId())) {
            responseDTO.setErrorCode(ErrorCodeEnum.INVALID_INPUTPARAM);
            return responseDTO;
        }

        OTAHotelResNumUpdateRQ otaHotelResNumUpdateRQ = new OTAHotelResNumUpdateRQ();
        //头标签属性
        otaHotelResNumUpdateRQ.setVersion(CtripOSCommonConstants.CTRIP_OS_STATIC_INFO_VERSION);
        otaHotelResNumUpdateRQ.setTimeStamp(CtripOSCommonUtil.dateToXMLGregorianCalendar(new Date()));
        //接口认证信息
        CtriposShopInfoDO shopInfoDO = InitData.shopIdShopMap.get(request.getShopId());

        POSType posType = new POSType();
        SourceType sourceType = new SourceType();
        SourceType.RequestorID requestorID = new SourceType.RequestorID();
        requestorID.setType(shopInfoDO.getType());
        requestorID.setID(shopInfoDO.getApiId());
        requestorID.setMessagePassword(shopInfoDO.getMessagePassword());
        CompanyNameType companyNameType = new CompanyNameType();
        companyNameType.setCode(shopInfoDO.getCode());
        companyNameType.setCodeContext(shopInfoDO.getCodeContext());
        requestorID.setCompanyName(companyNameType);
        sourceType.setRequestorID(requestorID);
        posType.setSource(sourceType);
        otaHotelResNumUpdateRQ.setPOS(posType);

        OTAHotelResNumUpdateRQ.HotelReservations hotelReservations = new OTAHotelResNumUpdateRQ.HotelReservations();
        HotelReservationType hotelReservation = new HotelReservationType();
        ResGlobalInfoType resGlobalInfo = new ResGlobalInfoType();
        ArrayOfHotelReservationIDType hotelReservationIDs = new ArrayOfHotelReservationIDType();
        // 携程订单号
        HotelReservationIDType idType1 = new HotelReservationIDType();
        idType1.setResIDType(UniqueIDEnum.UNIQUEID_501.key);
        idType1.setResIDValue(request.getCtripOrderCode());
        // 房仓订单号
        HotelReservationIDType idType2 = new HotelReservationIDType();
        idType2.setResIDType(UniqueIDEnum.UNIQUEID_502.key);
        idType2.setResIDValue(request.getOrderCode());
        // 酒店确认号
        HotelReservationIDType idType3 = new HotelReservationIDType();
        idType3.setResIDType(UniqueIDEnum.UNIQUEID_504.key);
        idType3.setResIDValue(request.getConfirmNo());
        hotelReservationIDs.getHotelReservationID().add(idType1);
        hotelReservationIDs.getHotelReservationID().add(idType2);
        hotelReservationIDs.getHotelReservationID().add(idType3);
        resGlobalInfo.setHotelReservationIDs(hotelReservationIDs);
        hotelReservation.setResGlobalInfo(resGlobalInfo);
        hotelReservations.setHotelReservation(hotelReservation);
        otaHotelResNumUpdateRQ.setHotelReservations(hotelReservations);
        OTAHotelResNumUpdateRS otaHotelResNumUpdateRS = ctripOSRemoteInvokeService.updateResConfirmationNum(otaHotelResNumUpdateRQ, request.getShopId());
        if (null == otaHotelResNumUpdateRS || null != otaHotelResNumUpdateRS.getErrors()) {
            responseDTO.setResult(0);
            responseDTO.setFailReason("更新携程国际订单确认号失败！");
            return responseDTO;
        }

        responseDTO.setResult(1);
        return responseDTO;
    }

    /**
     * 组装查询订单响应对象
     *
     * @param otaReadRS
     * @param orderDOS
     * @param readReservationMap
     */
    private void assemblyReadReservationResponse(OTAReadRS otaReadRS, List<OrderDO> orderDOS, Map<String, String> readReservationMap) {
        if (CollectionUtils.isEmpty(orderDOS) || MapUtils.isEmpty(readReservationMap)) {
            return;
        }

        List<HotelReservation> hotelReservations = new ArrayList<>();
        if (CollectionUtils.isEmpty(orderDOS) && !MapUtils.isEmpty(readReservationMap)) {
            // 如果没有查到任何订单，直接返回Rejected/failed
            for (Map.Entry<String, String> orderEntry : readReservationMap.entrySet()) {
                HotelReservation hotelReservation = new HotelReservation();
                hotelReservation.setResStatus("R");
                ResGlobalInfo resGlobalInfo = new ResGlobalInfo();
                List<HotelReservationID> hotelReservationIDs = new ArrayList<>();
                HotelReservationID ctripHotelReservationID = new HotelReservationID();
                ctripHotelReservationID.setResID_Type("501");
                ctripHotelReservationID.setResID_Value(orderEntry.getValue());
                hotelReservationIDs.add(ctripHotelReservationID);
                HotelReservationID fcHotelReservationID = new HotelReservationID();
                fcHotelReservationID.setResID_Type("502");
                fcHotelReservationID.setResID_Value(orderEntry.getKey());
                hotelReservationIDs.add(fcHotelReservationID);
                resGlobalInfo.setHotelReservationIDs(hotelReservationIDs);
                hotelReservation.setResGlobalInfo(resGlobalInfo);
                hotelReservations.add(hotelReservation);
            }
        } else {
            // 根据房仓订单状态，转换为ctrip订单状态
            Set<String> fcOrderCodeSet = new HashSet<>();
            for (OrderDO orderDO : orderDOS) {
                HotelReservation hotelReservation = new HotelReservation();
                String resStatus;
                if (null == orderDO.getOrderStatus()) {
                    resStatus = "R";
                } else if (orderDO.getOrderStatus().intValue() == OrderStatusEnum.TRADED.key) {
                    resStatus = "S";
                } else if (orderDO.getOrderStatus().intValue() == OrderStatusEnum.CANCELED.key) {
                    resStatus = "C";
                } else if (orderDO.getOrderStatus().intValue() == OrderStatusEnum.NEWORDER.key
                        || orderDO.getOrderStatus().intValue() == OrderStatusEnum.PROCESSING.key) {
                    resStatus = "P";
                } else {
                    resStatus = "R";
                }
                hotelReservation.setResStatus(resStatus);
                ResGlobalInfo resGlobalInfo = new ResGlobalInfo();
                List<HotelReservationID> hotelReservationIDs = new ArrayList<>();
                HotelReservationID ctripHotelReservationID = new HotelReservationID();
                ctripHotelReservationID.setResID_Type("501");
                ctripHotelReservationID.setResID_Value(readReservationMap.get(orderDO.getOrderCode()));
                hotelReservationIDs.add(ctripHotelReservationID);
                HotelReservationID fcHotelReservationID = new HotelReservationID();
                fcHotelReservationID.setResID_Type("502");
                fcHotelReservationID.setResID_Value(orderDO.getOrderCode());
                hotelReservationIDs.add(fcHotelReservationID);
                resGlobalInfo.setHotelReservationIDs(hotelReservationIDs);
                hotelReservation.setResGlobalInfo(resGlobalInfo);
                hotelReservations.add(hotelReservation);
                fcOrderCodeSet.add(orderDO.getOrderCode());
            }
            // 如果请求订单数和查询结果订单数不一致，则需要把在房仓没找到的订单输出到错误信息中
            if (readReservationMap.size() != fcOrderCodeSet.size()) {
                List<Error> errors = new ArrayList<>();
                for (Map.Entry<String, String> orderEntry : readReservationMap.entrySet()) {
                    if (!fcOrderCodeSet.contains(orderEntry.getKey())) {
                        Error error = new Error();
                        error.setCode(CtripOSErrorEnum.CtripOSError_245.getCode());
                        error.setShortText("Reservation " + orderEntry.getKey() + " is not found");
                        error.setType(CtripOSTypeEnum.CtripOSType_3.getType());
                        errors.add(error);
                    }
                }
                otaReadRS.setErrors(errors);
            }
        }
        otaReadRS.setHotelReservations(hotelReservations);
    }

    /**
     * 检验查询订单参数
     *
     * @param otaReadRQ
     * @param otaReadRS
     * @return
     */
    private CtriposShopInfoDO checkReadReservationParam(OTAReadRQ otaReadRQ, OTAReadRS otaReadRS) {
        // 1. 检验参数信息
        if (null == otaReadRQ
                || CollectionUtils.isEmpty(otaReadRQ.getReadRequests())) {
            otaReadRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_10, CtripOSErrorEnum.CtripOSError_315, "Read reservation param is empty"));
            return null;
        }
        for (ReadRequest readRequest : otaReadRQ.getReadRequests()) {
            if (null == readRequest || CollectionUtils.isEmpty(readRequest.getUniqueIDs())) {
                otaReadRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_10, CtripOSErrorEnum.CtripOSError_315, "Read reservation param is empty"));
                return null;
            }
            for (UniqueID uniqueID : readRequest.getUniqueIDs()) {
                if (null == uniqueID || null == uniqueID.getId() || null == uniqueID.getType()) {
                    otaReadRS.setErrors(CtripOSErrorBuilder.buildErrors(CtripOSTypeEnum.CtripOSType_10, CtripOSErrorEnum.CtripOSError_315, "Read reservation param is empty"));
                    return null;
                }
            }
        }

        // 2. 检验接口认证账号信息
        return getCommonCtripOSAuthConfig(otaReadRQ.getPos(), otaReadRS);
    }

    /**
     * 根据请求的guestCounts获取成人数、儿童数
     *
     * @param guestCounts
     * @return
     */
    private HotelGuestCountDTO getAdultOrChildCount(List<GuestCount> guestCounts) {
        HotelGuestCountDTO guestCountDTO = new HotelGuestCountDTO();
        Integer adultCount = null;
        Integer childCount = null;
        for (GuestCount guestCount : guestCounts) {
            if (null == guestCount.getAgeQualifyingCode() || guestCount.getAgeQualifyingCode() == 10) {
                adultCount = guestCount.getCount();
            } else if (guestCount.getAgeQualifyingCode() == 8) {
                childCount = guestCount.getCount();
            }
        }
        guestCountDTO.setAdultCount(adultCount);
        guestCountDTO.setChildCount(childCount);
        return guestCountDTO;
    }
}
